#include <atomic>
#include <chrono>
#include <cstdlib>
#include <errno.h>
#include <iomanip>
#include <iostream>
#include <list>
#include <numa.h>
#include <numaif.h>
#include <sched.h>
#include <string.h>
#include <thread>

#include "mini-log.hpp"

using namespace mini_log;
using namespace std::chrono;
using namespace std::chrono_literals;

using Results_t = std::list<uint64_t>;
using Threads_t = std::list<std::thread>;

// 显示当前线程在哪个cpu、哪个numa node上, 以及buf在哪个numa node
int ShowLocation( uint64_t* buffer );

// 往buffer里面填充东西,确保正式开始测试时,最需要的数据不在cache里,必须去内存读取
void FillBuffer( uint64_t* buffer, size_t elements );

// 子线程主体
void RunnerBody( int id, size_t elements, uint64_t* buffer, uint64_t* result );

// 显示用法后退出
void ShowHelpExit( const str_t& hint = "" );
// 解析命令行参数
void ParseCli( int arg_count, const char* const* const arg_vals );

std::atomic_int		s_readies { 0 };	// 多少个线程已经就绪了
std::atomic_int		s_finishs { 0 };	// 多少个线程已经完成了
std::atomic_bool	s_go { false };		// 你们可以开始了
std::atomic_bool	s_exit { false };	// 你们可以退出了

int		s_threads { -1 };
int		s_MiB_1bf { -1 };
bool	s_u_alloc { false };
bool	s_on1node { false };

int main( int argc_, char** args_ ) {
	ParseCli( argc_, args_ );
	reg_thread( "主线程" );
	// 先看一眼主线程在哪个node上
	ShowLocation( nullptr );

	log_stdout( "线程数:" + fmt( s_threads )
				+ ", 每buffer大小:" + fmt( s_MiB_1bf ) + "MiB." );
	size_t bytes_of_1buf = s_MiB_1bf * 1024 * 1024;
	size_t elements = bytes_of_1buf / sizeof( uint64_t );
	// U64_u seed { system_clock::now().time_since_epoch().count() };
	// std::srand( seed.dw0 );

	log_stdout( "启动子线程们..." );
	Threads_t threads;
	Results_t results;
	for( int i = 0; i < s_threads; ++i ) {
		uint64_t* tmp_buf = nullptr;
		if( s_u_alloc )
			tmp_buf = new uint64_t[elements];
		threads.emplace_back(
			RunnerBody, i, elements, tmp_buf, & results.emplace_back() );
	}

	log_stdout( "等待子线程们填充它们的buffer..." );
	while( s_readies.load( std::memory_order_acquire ) < s_threads )
		std::this_thread::yield();
	std::this_thread::sleep_for( 1s );

	log_stdout( "开始..." );
	auto beg = steady_clock::now();
	// 发出统一"go"信号
	s_go.store( true, std::memory_order_release );
	// 等待全部完成(busy check)
	while( s_finishs.load( std::memory_order_acquire ) < s_threads );

	auto end = steady_clock::now();
	double ns = ( end - beg ).count();
	double bw = s_MiB_1bf * s_threads * ( 1000000000. / 1024 ) / ns;
	log_stdout( "完成。用时:" + fmt( ns, 0, 0, 3, ' ', ',' )
				+ "ns, 带宽:" + fmt( bw, 0, 3, 3, ' ', ',' ) + "GiB/s." );

	// 暂停一下(不慌释放内存),以便外部程序(比如top)观察我们的内存占用情况
	std::this_thread::sleep_for( 20s );

	s_exit.store( true );
	for( auto& t : threads )
		t.join();

	return EXIT_SUCCESS;
}

int ShowLocation( uint64_t* buf_ ) {

	uint32_t cpu { 0xFFFFFF }, node { 0xFFFFFF };
	if( getcpu( &cpu, &node ) != 0 ) {
		int		err_num = errno;
		str_t	err_str = strerror( err_num );
		log_stderr( "getcpu()时出错:\"" + err_str + "\"(" + fmt( err_num ) + ")!" );
		exit( EXIT_FAILURE );
	}

	log_stdout( "我现运行在cpu" + fmt( cpu ) + "上, node" + fmt( node ) );
	if( buf_ == nullptr )
	return node;

	void*	pages[1] { buf_, };
	int		stats[1] { -1, };
	if( move_pages( 0, 1, pages, NULL, stats, MPOL_MF_MOVE ) != 0 )
		throw std::runtime_error( "move_pages失败!" );
//	log_stdout( "我的buffer在node" + fmt( stats[0], 0, 0 ) + "上." );
	if( static_cast<uint32_t>( stats[0] ) != node )
		log_stderr( "我的buffer没有分配在和我相同的node上!" );
	return node;
};

void FillBuffer( uint64_t* buf_, size_t cnt_ ) {
//	U64_u	u64;
	for( size_t i = 0; i < cnt_; ++i ) {
//		u64.dw0 = std::rand(); u64.dw1 = std::rand();
		buf_[i] = i + 1;
	}
};

void RunnerBody( int id_, size_t cnt_, uint64_t* buf_, uint64_t* res_ ) {
	str_t my_name = "线程" + fmt( id_, 4, 0, 0, '0' );
	reg_thread( my_name );

	if( buf_ == nullptr )
		buf_ = new uint64_t[cnt_];

	FillBuffer( buf_, cnt_ );
	ShowLocation( buf_ );
	++s_readies;

	// 等待统一口令"go",再开始...
	while( !s_go.load( std::memory_order_acquire ) );

	auto		beg = steady_clock::now();
	// 用时间作为初值,否则会被乱序执行或优化掉
	uint64_t	r = beg.time_since_epoch().count();
	for( size_t i = 0; i < cnt_; ++i )
		r ^= buf_[i];
	*res_ = r;
	++s_finishs;
	// lg_info << "我用了:" << ( steady_clock::now() - beg ).count()
	// 		<< "ns,做了:" << cnt_ << "次.";

	// 暂停一下(不慌释放内存),以便外部程序(比如top)观察我们的内存占用情况
	while( !s_exit.load( std::memory_order_acquire ) )
		std::this_thread::sleep_for( 1ms );
	delete[] buf_;
};

void ShowHelpExit( const str_t& msg_ ) {
	if( !msg_.empty() )
		std::cerr << msg_ << '\n' << std::endl;

	std::cerr << R"TEXT(用法:
	-h (--help)       : 显示用法后退出
	-t (--threads)      <线程数>(必填)
	-m (--mb-1buf)      <单个buffer大小MiB>(必填)
	-s (--use-shm)      使用SHM共享内存(可选,默认使用new内存)
	-u (--uni-alloc)    由主线程统一分配内存(可选,默认让各线程自己分配)
	-n (--same-node)    全部运行于同一node(可选,默认让OS分配)
)TEXT" << std::endl;

	exit( EXIT_FAILURE );
};

void ParseCli( int argc_, const char* const* const args_ ) {
	bool opt_err = false;

	for( int i = 1; i < argc_; ++i ) {
		str_t argv = args_[i];

		if( argv == "-h" || argv == "--help" ) {
			ShowHelpExit();

		} else if( argv == "-t" || argv == "--threads" ) {
			if( !( opt_err = ++i >= argc_ ) )
				s_threads = std::atoi( args_[i] );

		} else if( argv == "-m" || argv == "--mb-1buf" ) {
			if( !( opt_err = ++i >= argc_ ) )
				s_MiB_1bf = std::atoi( args_[i] );

		} else if( argv == "-u" || argv == "--uni-alloc" ) {
			s_u_alloc = true;

		} else if( argv == "-n" || argv == "--same-node" ) {
			s_on1node = true;

		} else
			ShowHelpExit( '"' + argv + "\"是无法识别的选项,无法继续!" );
		if( opt_err )
			ShowHelpExit( '"' + argv + "\"选项后面需要参数值,无法继续!" );
	};

	if( s_threads < 1 )
		ShowHelpExit( "threads必须指定,无法启动!" );
	if( s_MiB_1bf < 1 )
		ShowHelpExit( "buffer尺寸必须指定,无法启动!" );
};

// kate: indent-mode cstyle; indent-width 4; replace-tabs off; tab-width 4;
